﻿#region Header
//
//	Project:		AllegroNET
//	Description:	Silverlight port of Allegro (http://alleg.sourceforge.net/)
//
//	File I/O - internal filesystem
//
//	Copyright © 2010 Andrey Gayvoronsky
//
//	Please read the License.txt for details.
//
#endregion

using System;
using System.IO.IsolatedStorage;
using System.IO;

namespace AllegroNET
{
	public class PACKFILE : IDisposable
	{
		private const int F_BUF_SIZE = 0x8000; // 32K buffer for read/write buffers

		public bool has_parent { get; private set; }
		public int parent_offset { get; private set; }

		public bool is_packed
		{
			get { return _is_packed; }
			set
			{
				_is_packed = value;

				if (_is_packed)
				{
					if (packed == null)
						packed = new byte[F_BUF_SIZE];

					if (lzss == null)
						lzss = new LZSS();

					packed_len = data_len = data_ptr = 0;
					packed_total = (int)(file.Length - file.Position);
				}
			}
		}

		public IsolatedStorageFileStream file { get; private set; }

		private bool _is_packed;
		private byte[] packed = null;
		private int packed_len;
		private int packed_total;

		private byte[] data = new byte[F_BUF_SIZE];
		private int data_len = 0;
		private int data_ptr = 0;
		private LZSS lzss = null;

		public PACKFILE(string filename)
		{
			has_parent = false;
			is_packed = false;

			IsolatedStorageFile iso = Allegro.get_file_storage();
			file = iso.OpenFile(filename, FileMode.Open);
		}
		
		public int seek(int offset)
		{
			int total = 0;
			int seek_size;
			int state = 0;

			while (total < offset)
			{
				if (data_len == 0 || data_ptr >= data_len)
					if (!refresh_buffer())
					{
						state = total;
						break;
					}

				seek_size = System.Math.Min(data_len - data_ptr, System.Math.Min(data_len, offset - total));
				total += seek_size;
				data_ptr += seek_size;
			}

			return state;
		}

		public int read(byte[] dst, int size)
		{
			int total = 0;
			int copy_size;

			while (total < size)
			{
				if (data_len == 0 || data_ptr >= data_len)
					if (!refresh_buffer())
						break;

				copy_size = System.Math.Min(data_len - data_ptr, System.Math.Min(data_len, size - total));
				Buffer.BlockCopy(data, data_ptr, dst, total, copy_size);
				total += copy_size;
				data_ptr += copy_size;
			}

			return total;
		}

		public int getc()
		{
			if (data_len == 0 || data_ptr >= data_len)
				if (!refresh_buffer())
					return -1;

			return data[data_ptr++];
		}

		private bool refresh_buffer()
		{
			data_len = 0;
			data_ptr = 0;

			if (is_packed)
			{
				if (lzss.unpack.src_offset >= packed_len)
				{
					packed_len = file.Read(packed, 0, packed.Length);
					lzss.unpack.src_offset = 0;
				}

				if (packed_len > 0)
					data_len = (int)lzss.decompress(data, packed, packed_total);
			}
			else
			{
				data_len = file.Read(data, 0, data.Length);
			}

			return ((data_len > 0) ? true : false);
		}

		#region IDisposable Members
		private bool isDisposed = false;
		~PACKFILE()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		protected virtual void Dispose(bool disposing)
		{
			if (!isDisposed)
			{
				if (disposing)
				{
					data = null;
					lzss = null;
					packed = null;
				}

				if (!has_parent && file != null)
					file.Close();

				file = null;
				isDisposed = true;
			}
		}
		#endregion
	}

	public partial class Allegro
	{
		public enum FileStorageType
		{
			ApplicationStorage,
			SiteStorage,
			UnknownStorage
		}

		private static FileStorageType storage_type = FileStorageType.UnknownStorage;

		/* init_file_storage:
		 * Before reading any DATAFILE you must initialize file storage for your application.
		 * It can be special storage for application or it can be storage for whole domain. 
		 * For more information read about IsolatedStorageFile on MSDN for Silverlight.
		 */
		static public void init_file_storage(FileStorageType type)
		{
			storage_type = type;
		}

		/* get_file_storage:
		 * Helper function for any file operation. Used internally.
		 */
		static public IsolatedStorageFile get_file_storage()
		{
			if (storage_type == FileStorageType.ApplicationStorage)
				return IsolatedStorageFile.GetUserStoreForApplication();

			if (storage_type == FileStorageType.SiteStorage)
				return IsolatedStorageFile.GetUserStoreForSite();

			throw new Exception("Unknown type of file storage. You must initialize it via init_file_storage()");
		}

		/* pack_mgetl:
		 * Read 32bit number in big-endian (Motorola) format.
		 */
		static public long pack_mgetl(PACKFILE f)
		{
			System.Diagnostics.Debug.Assert(f != null);

			long b1, b2, b3, b4;

			if ((b1 = pack_getc(f)) != -1)
				if ((b2 = pack_getc(f)) != -1)
					if ((b3 = pack_getc(f)) != -1)
						if ((b4 = pack_getc(f)) != -1)
							return ((b1 << 24) | (b2 << 16) | (b3 << 8) | b4);

			return -1;
		}

		/* pack_mgetw:
		 *  Reads a 16 bit int from a file, using motorola byte-ordering.
		 */
		static public int pack_mgetw(PACKFILE f)
		{
			System.Diagnostics.Debug.Assert(f != null);

			int b1, b2;

			if ((b1 = pack_getc(f)) != -1)
				if ((b2 = pack_getc(f)) != -1)
					return ((b1 << 8) | b2);

			return -1;
		}

		/* pack_igetl:
		 *  Reads a 32 bit long from a file, using intel byte ordering.
		 */
		static public long pack_igetl(PACKFILE f)
		{
			System.Diagnostics.Debug.Assert(f != null);

			long b1, b2, b3, b4;

			if ((b1 = pack_getc(f)) != -1)
				if ((b2 = pack_getc(f)) != -1)
					if ((b3 = pack_getc(f)) != -1)
						if ((b4 = pack_getc(f)) != -1)
							return (((long)b4 << 24) | ((long)b3 << 16) | ((long)b2 << 8) | (long)b1);

			return -1;
		}

		/* pack_igetw:
		 *  Reads a 16 bit word from a file, using intel byte ordering.
		 */
		static public int pack_igetw(PACKFILE f)
		{
			int b1, b2;
			System.Diagnostics.Debug.Assert(f != null);

			if ((b1 = pack_getc(f)) != -1)
				if ((b2 = pack_getc(f)) != -1)
					return ((b2 << 8) | b1);

			return -1;
		}

		/* pack_getc:
		 *  Returns the next character from the stream f, or EOF if the end of the
		 *  file has been reached.
		 */
		static public int pack_getc(PACKFILE f)
		{
			System.Diagnostics.Debug.Assert(f != null);
			return f.getc();
		}

		/* pack_fread:
		 *  Reads n bytes from f and stores them at dst. Returns the 
		 *  number of bytes read, which will be less than n if EOF is reached or an 
		 *  error occurs.
		 */
		static public int pack_fread(byte[] dst, int size, PACKFILE f)
		{
			System.Diagnostics.Debug.Assert(f != null);
			return f.read(dst, size);
		}

		/* pack_fseek:
		 *  Like the stdio fseek() function, but only supports forward seeks 
		 *  relative to the current file position.
		 *  
		 * On success returns 0 and non-zero on failure
		 */
		static public int pack_fseek(PACKFILE f, int offset)
		{
			System.Diagnostics.Debug.Assert(f != null);
			System.Diagnostics.Debug.Assert(offset >= 0);

			return f.seek(offset);
		}

		/* pack_fopen:
		 *  On success, fopen() returns a pointer to a file structure, and on error
		 *  it returns NULL. Works fine with both types - packed and raw files.
		 *  
		 * mode is ignored right now. only reading is supported.
		 */
		static public PACKFILE pack_fopen(string filename, string mode)
		{
			if (filename == null)
				return null;

			PACKFILE f = null;

			try
			{
				f = new PACKFILE(filename);

				long header = pack_mgetl(f);

				if (header != F_PACK_MAGIC && header != F_NOPACK_MAGIC)
					throw new Exception("Wrong or unsupported format of DATAFILE");

				//is file packed globally?
				if (header == F_PACK_MAGIC)
				{
					f.file.Seek(sizeof(int), System.IO.SeekOrigin.Begin);
					f.is_packed = true;
				}

				int type = (int)pack_mgetl(f);

				try
				{
					if (type != DAT_MAGIC)
						throw new Exception("Wrong or unsupported type of DATAFILE");
				}
				catch (Exception e)
				{
					throw e;
				}
			}
			catch (Exception e)
			{
				f.Dispose();
				f = null;
				System.Windows.MessageBox.Show(e.Message);
			}

			return f;
		}

		/* pack_fclose:
		 * Just for compatibility with original library. 
		 * You can use Dispose instead of that function.
		 */
		static public int pack_fclose(PACKFILE f)
		{
			if (f != null)
			{
				f.Dispose();
				f = null;
			}

			return 0;
		}
	}
}
